﻿/*
 * RssService
 * 
 * This is the core Rss Domain Service provides subsribe and update rss functions
 * 
 * This service need two external components:
 *      RssServiceConnector which implements IRssServiceConnector that handles all web requests
 *          By default, this connector implemention is placed in SNSpirit.Infrastructure lib
 *      RssFeedRepository which implements IRssFeedRepository that help persist rss feed
 *          By default, this repository implemention is placed in SNSpirit.Infrastructure lib
 * 
 */

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading.Tasks;
using SNSpirit.Domain.User;

namespace SNSpirit.Domain.Rss
{
    public class RssService : DomainService
    {
        public static readonly TimeSpan DefaultSubscribeTimeInterval = TimeSpan.FromMinutes(10);

        public RssService(IRssServiceConnector connector, IRssFeedRepository feedRepository)
        {
            if (connector == null)
                throw new ArgumentNullException("connector");
            if (feedRepository == null)
                throw new ArgumentNullException("feedRepository");
            // init
            this._connector = connector;
            this._feedRepository = feedRepository;
            this._feeds = new List<RssFeed>();
            this._tracer = new RssFeedTracer(_connector);
            this._tracer.RssTraceErrorOccurred += new EventHandler<RssTraceErrorOccurredEventArgs>(RssTraceErrorOccurred);
            this.Feeds = new ReadOnlyCollection<RssFeed>(this._feeds);
            // async fetch
            Task.Factory.StartNew(new Action(() =>
                {
                    lock (this)
                    {
                        this._feeds = this._feedRepository.GetRssFeeds().ToList();
                    }
                    this.OnFeedUpdated();
                }));
        }
        /// <summary>
        /// Subscribe a rss
        /// </summary>
        /// <param name="rssUri"></param>
        public void Subscribe(Uri rssUri)
        {
            this.Subscribe(rssUri, null, DefaultSubscribeTimeInterval);
        }
        /// <summary>
        /// Subscribe a rss
        /// </summary>
        /// <param name="rssUri"></param>
        /// <param name="updateInterval"></param>
        public void Subscribe(Uri rssUri, TimeSpan updateInterval)
        {
            this.Subscribe(rssUri, null, updateInterval);
        }
        /// <summary>
        /// Subscribe a rss
        /// </summary>
        /// <param name="rssUri"></param>
        /// <param name="user"></param>
        public void Subscribe(Uri rssUri, UserEntity user)
        {
            this.Subscribe(rssUri, user, DefaultSubscribeTimeInterval);
        }
        /// <summary>
        /// Subscribe a rss
        /// </summary>
        /// <param name="rssUri"></param>
        /// <param name="user"></param>
        /// <param name="updateInterval"></param>
        public void Subscribe(Uri rssUri, UserEntity user, TimeSpan updateInterval)
        {
            if (rssUri == null)
                throw new ArgumentNullException("rssUri");

            RssFeed rssFeed = null;

            lock (this)
            {
                // check if the feed is already exists
                var r = this._feeds.Where(p => p.UpdateUri == rssUri);
                if (r.Count() == 0)
                {
                    // not exists, create new one
                    rssFeed = new RssFeed()
                    {
                        UpdateUri = rssUri,
                        User = user,
                        UpdateInterval = updateInterval
                    };
                }
                else
                {
                    // exist
                    rssFeed = r.First();
                    // update
                    rssFeed.UpdateInterval = updateInterval;
                    rssFeed.User = user;
                }
                // add to list
                this._feeds.Add(rssFeed);
            }
            // trace
            this._tracer.Trace(rssFeed);
            // add or update to repository
            this._feedRepository.AddOrUpdateRssFeeds(new RssFeed[] { rssFeed });
            // update
            this.OnFeedUpdated();
        }

        public void Unsubscribe(RssFeed rssFeed)
        {
            if (rssFeed == null)
                throw new ArgumentNullException("rssFeed");
            this.Unsubscribe(rssFeed.UpdateUri);
        }

        public void Unsubscribe(Uri subscribeUri)
        {
            if (subscribeUri == null)
                throw new ArgumentNullException("subscribeUri");

            RssFeed feed = null;

            lock (this)
            {
                // find it
                var r = this._feeds.Where(p => p.UpdateUri == subscribeUri);
                if (r.Count() == 0)
                    // not found
                    return;
                // unsubscribe
                feed = r.First();
                this._feeds.Remove(feed);
            }
            // remove tracing
            this._tracer.RemoveTracing(feed);
            // remove from repository
            this._feedRepository.RemoveRssFeeds(new RssFeed[] { feed });
            // update
            this.OnFeedUpdated();
        }

        public RssFeed AchieveRss(Uri updateUri)
        {
            RssFeed feed = new RssFeed();

            feed.UpdateUri = updateUri;

            this.UpdateRss(feed);

            return feed;
        }

        public RssFeed AchieveRss(Uri updateUri, UserEntity user)
        {
            RssFeed feed = new RssFeed();

            feed.UpdateUri = updateUri;

            this.UpdateRss(feed);

            return feed;
        }

        public void UpdateRss(RssFeed feed)
        {
            if (feed.UpdateUri == null)
                throw new ArgumentException("UpdateUri is required");

            try
            {
                var r = this._connector.Update(feed);
                if(r)
                    feed.NotifyUpdated();
            }
            catch (Exception error)
            {
                throw new RssUpdateFailedException(feed.UpdateUri, feed.User, error);
            }
        }

        public ReadOnlyCollection<RssFeed> Feeds { get; private set; }

        public event EventHandler FeedUpdated;

        #region protected

        protected virtual void OnFeedUpdated()
        {
            if (this.FeedUpdated != null)
                this.FeedUpdated(this, new EventArgs());
        }

        #endregion

        #region private

        private IRssServiceConnector _connector;

        private IRssFeedRepository _feedRepository;

        private RssFeedTracer _tracer;

        private List<RssFeed> _feeds;

        private void RssTraceErrorOccurred(object sender, RssTraceErrorOccurredEventArgs e)
        {
            // log
            DomainUtillity.TraceEvent(this, TraceEventType.Error, string.Format("Rss tracer error:{0} for {1}", e.Error.Message, e.Feed));
            // fire event
            this.OnBackgroundJobErrorOccurred(new RssUpdateFailedException(e.Feed.UpdateUri, e.Feed.User, e.Error));
        }

        #endregion
    }
}
